//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/io/cunarchiver.h>
#include <buola/io.h>

#include <archive_entry.h>

namespace buola { namespace io {

CUnarchiverStream::CUnarchiverStream(::archive *pArchive)
    :   mArchive(pArchive)
    ,   mNextBlockOffset(0)
{
    mREnd=mRPtr=nullptr;
    mWEnd=mWPtr=nullptr;
}

CUnarchiverStream::~CUnarchiverStream()
{
}

void CUnarchiverStream::ResetForNextFile()
{
    mNextBlockOffset=0;
    mREnd=mRPtr=nullptr;
}

CResult CUnarchiverStream::DoRead(uint8_t *pData, size_t pLen)
{
    ssize_t lRead=0;

    if(mRPtr<mREnd)
    {
        //copy what's available... it's guaranteed that not everything is available
        lRead=mREnd-mRPtr;
        memcpy(pData,mRPtr,lRead);
        mRPtr=mREnd;
        pData+=lRead;
        pLen-=lRead;
    }

    while(pLen)
    {
        CResult lResult=DoUnderflow();
        
        if(!lResult.Count())
            return CResult(lRead,lResult.Flags());
        
        size_t lToRead=std::min(pLen,lResult.Count());
        
        lRead+=lToRead;
        memcpy(pData,mRPtr,lToRead);
        mRPtr+=lToRead;
        pData+=lToRead;
        pLen-=lToRead;
    }
    
    return CResult(lRead);
}

CResult CUnarchiverStream::DoIgnore(size_t pLen)
{
    ssize_t lIgnored=0;

    if(mRPtr<mREnd)
    {
        //copy what's available... it's guaranteed that not everything is available
        lIgnored=mREnd-mRPtr;
        mRPtr=mREnd;
        pLen-=lIgnored;
    }

    while(pLen)
    {
        CResult lResult=DoUnderflow();
        
        if(!lResult.Count())
            return CResult(lIgnored,lResult.Flags());
        
        size_t lToIgnore=std::min(pLen,lResult.Count());
        
        lIgnored+=lToIgnore;
        mRPtr+=lToIgnore;
        pLen-=lToIgnore;
    }
    
    return CResult(lIgnored);
}

CResult CUnarchiverStream::DoUnderflow()
{
    size_t lSize=0;
    off_t lOffset=0;
    const void *lBuffer=nullptr;
    int lStatus=archive_read_data_block(mArchive,&lBuffer,&lSize,&lOffset);
    
    if(lStatus==ARCHIVE_OK||lStatus==ARCHIVE_WARN||lStatus==ARCHIVE_EOF) //ok
    {
        if(lOffset!=mNextBlockOffset)
            throw XData("invalid offset in unarchiver stream");

        mNextBlockOffset=lOffset+lSize;
        mRPtr=static_cast<const uint8_t*>(lBuffer);
        mREnd=mRPtr+lSize;
        
        if(lSize)
            return CResult(lSize);
        else
            return CResult(lSize,EResult::ENDOFFILE);
    }
    
    throw XIO("error reading from unarchiver");
}

CResult CUnarchiverStream::DoWrite(const uint8_t *pData, size_t pLen)
{
    throw XInvalid("can't write to unarchiver stream");
}

void CUnarchiverStream::DoFlush()
{
    throw XInvalid("can't flush unarchiver stream");
}

off_t CUnarchiverStream::DoSeek(off_t pPos, ESeek pMode)
{
    throw XInvalid("can't seek unarchiver stream");
}

off_t CUnarchiverStream::DoTell()
{
    throw XInvalid("can't tell unarchiver stream");
}
    
__LA_SSIZE_T CUnarchiver::ReadCallback(archive *pArchive, void *pData, const void **pBuffer)
{
    ///\todo report errors
    CUnarchiver *lU=static_cast<CUnarchiver*>(pData);

    const uint8_t *lBlock;
    size_t lRead=lU->mStream->ReadNextBlock(lBlock).Count();
    *pBuffer=lBlock;
    return lRead;
}

__LA_INT64_T CUnarchiver::SkipCallback(archive *pArchive, void *pData, __LA_INT64_T pRequest)
{
    ///\todo report errors
    CUnarchiver *lU=static_cast<CUnarchiver*>(pData);

    size_t lSkip=lU->mStream->Ignore(pRequest).Count();
    return lSkip;
}
    
__LA_INT64_T CUnarchiver::SeekCallback(archive *pArchive, void *pData, __LA_INT64_T pRequest, int pWhence)
{
    ///\todo verify and use this
    CUnarchiver *lU=static_cast<CUnarchiver*>(pData);
    
    if(pWhence==SEEK_CUR)
        return lU->mStream->Seek(pRequest,ESeek::RELATIVE);
    else if(pWhence==SEEK_END)
        return lU->mStream->Seek(pRequest,ESeek::END);
    else
        return lU->mStream->Seek(pRequest,ESeek::ABSOLUTE);
}

CUnarchiver::CUnarchiver(PBufferStream pStream)
    :   mStream(std::move(pStream))
    ,   mArchive(archive_read_new())
    ,   mEntry(nullptr)
{
    archive_read_support_filter_all(mArchive);
    archive_read_support_format_all(mArchive);
    archive_read_set_callback_data(mArchive,this);
    archive_read_set_read_callback(mArchive,ReadCallback);
    archive_read_set_skip_callback(mArchive,SkipCallback);
//    archive_read_set_seek_callback(mArchive,SeekCallback);
    archive_read_open1(mArchive);
}
    
CUnarchiver::CUnarchiver(const CURI &pURI)
    :   CUnarchiver(open_buffered(pURI,io::EMode::READ))
{
}

CUnarchiver::~CUnarchiver()
{

}

bool CUnarchiver::Next()
{
    if(mUnarchiverStream)
        mUnarchiverStream->ResetForNextFile();
    
    int lResult=archive_read_next_header(mArchive,&mEntry);
    
    if(lResult==ARCHIVE_WARN)
    {
        msg_warn() << "warning extracting from archive\n";
        return true;
    }
    else if(lResult==ARCHIVE_OK)
    {
        return true;
    }
    
    mEntry=nullptr;
    
    if(lResult==ARCHIVE_EOF)
        return false;
    
    throw XData("error in CUnarchiver::Next");
}

std::string CUnarchiver::GetPath()
{
    if(!mEntry) throw XInvalid("no entry in CUnarchiver");
    
    return archive_entry_pathname(mEntry);
}

size_t CUnarchiver::GetFileSize()
{
    if(!mEntry) throw XInvalid("no entry in CUnarchiver");

    return archive_entry_size(mEntry);
}

const PUnarchiverStream &CUnarchiver::GetStream()
{
    if(!mUnarchiverStream)
        mUnarchiverStream=new CUnarchiverStream(mArchive);
    
    return mUnarchiverStream;
}


/*namespace io*/ } /*namespace buola*/ }
